# -- coding: utf-8 --
"""
# MIAHX© 交互框架信息表示模型消息的 FastDDS 发布器

**模块：MIAHX© / 框架基础服务层（FFST） / 交互框架信息表示模型（IFIRM）**

Version: 0.4.10,
Last updated: October 22, 2024

## Contributor(s)
* YAO Naiming <[naiming@iscas.ac.cn](mailto:naiming@iscas.ac.cn)>

## Change log
### v0.4.10
- .22: [修复] _fill_attributes 方法增加异常处理
- .13: [更新] 适配新的消息定义，新增 srate(采样率) 和 schns(通道名称) 字段
- .9: [更新] packed 模式新增 timestamps 和 sns 字段，描述数据项的时间序列信息
- .2: [更新] 调整 write 方法实现，支持 constant 和 packed 模式
### v0.4.9
- .28: [优化] 日志
### v0.4.7
- .24: [更新] Writer 类暴露 domain 字段，便于 MiahxMsgHeader.domain 写入
### v0.4.5
- .16: [更新] 支持共享 participant
### v0.4.5
- .15: [更新] 新增描述数据来源的 tag 属性，一般表示传感器实例编码
### v0.4.5
- .5: [新增] 初始实现

---
Copyright © 2024-2025 MIAHX Contributors, ISCAS, and ASIC. All Rights Reserved.
Licensed under Apache-2.0.
"""

import socket
from threading import Condition
from typing import Union, Any

import miahx_fastdds as dds

from .participant import *

_log = logger.bind(module_name=os.path.basename(__file__))


class WriterListener(fastdds.DataWriterListener):
    def __init__(self, writer):
        self._writer = writer
        super().__init__()

    def on_publication_matched(self, datawriter, info):
        matched = 'matched' if 0 < info.current_count_change else 'unmatched'
        _log.debug(f"Publisher {matched} subscriber {info.last_subscription_handle}")

        self._writer._cvDiscovery.acquire()
        if 0 < info.current_count_change:
            self._writer._matched_reader += 1
        else:
            self._writer._matched_reader -= 1
        self._writer._cvDiscovery.notify()
        self._writer._cvDiscovery.release()


class Writer:
    def __init__(self, participant: Participant,
                 topic_name: str, topic_data_type: fastdds.TopicDataType,
                 stag: int = None, srate: int = None, schns: list[str] = None):
        """
        创建 DataWriter，发送 Topic 数据。
        如果 Participant 中尚未创建 Topic 和 Publisher，则新建。

        :param participant: 参与者对象
        :param topic_name: 主题名称，由 Topics 类定义
        :param topic_data_type: 主题数据类型，由 IDL 定义并包装为 Python 类型
        :param stag: 传感器标签，由 STag 类型生成
        :param srate: 传感器采样率，单位 Hz
        :param schns: 传感器数据通道定义
        """
        if participant is None:
            raise ValueError('participant')

        self._stag = stag
        self._srate = srate
        self._schns = schns

        self.domain = participant.domain
        self.topic_name = topic_name
        self.topic_data_type = topic_data_type
        self._matched_reader = 0
        self._cvDiscovery = Condition()

        topic = participant.add_topic(topic_name, topic_data_type)
        publisher = participant.add_publisher(PublisherQosTypes.DEFAULT)
        listener = WriterListener(self)
        w_qos = fastdds.DataWriterQos()
        # wqos.history().depth = 10
        publisher.get_default_datawriter_qos(w_qos)
        self._writer = publisher.create_datawriter(topic, w_qos, listener)

    def wait_discovery(self):
        self._cvDiscovery.acquire()
        _log.debug("Writer is waiting discovery...")

        self._cvDiscovery.wait_for(lambda: self._matched_reader != 0)
        self._cvDiscovery.release()
        _log.debug("Writer discovery finished...")

    def _create_msg_header(self):
        header = dds.MiahxMsgHeader()
        header.domain(self.domain)
        header.source(socket.gethostname())
        return header

    def _create_frame_header(self, uts: float, iid: int):
        assert uts >= 0.0 and iid >= 0
        header = dds.DataFrameHeader()
        header.header(self._create_msg_header())
        header.iid(iid)
        header.uts(uts)
        if self._stag:
            header.stag(self._stag)
        if self._srate:
            assert self._srate > 0
            header.srate(self._srate)
        if self._schns:
            assert len(self._schns) > 0
            header.schns(self._schns)
        return header

    def _fill_attributes(self, sample, sdata: dict):
        n = 0
        for key, val in sdata.items():
            attr = getattr(sample, key, None)
            if attr and val is not None:
                try:
                    attr(val)
                except Exception as ex:
                    _log.warning(f'{ex} ({type(ex).__name__})')
                n += 1
        # assert n == len(self._schns), f'expect {len(self._schns)} channels, but got {n}'


class PackableDataFrameWriter(Writer):
    def __init__(self, participant: Participant, topic_name: str,
                 topic_data_type: fastdds.TopicDataType, stag: str = None):
        super().__init__(participant, topic_name, topic_data_type, stag)

    def write(self, sample: Union[Any, list[Any]], uts: float = None, iid: int = None):
        try:
            create_data = getattr(dds, self.topic_data_type.getName())
            frame = create_data()
            frame.header(self._create_frame_header(uts, iid))
            if hasattr(frame, 'item'):
                frame.item(self._create_item(sample))
            elif hasattr(frame, 'items'):
                timestamps, sns, items = [], [], []
                for s in sample:
                    timestamps.append(s['TIMESTAMP'])
                    sns.append(s['SEQ'])
                    items.append(self._create_item(s))
                frame.timestamps(timestamps)
                frame.sns(sns)
                frame.items(items)
            else:
                raise NameError('lacks of .item or .items')
            self._writer.write(frame)
        except Exception as ex:
            _log.error(f'{ex} ({type(ex).__name__})')

    def _create_item(self, sample):
        raise NotImplementedError()
